package com.magenta.maxoptra.web.api.stores;

import com.magenta.maxoptra.gis.GeoException;
import com.magenta.maxoptra.gis.commons.Logger;
import com.magenta.maxoptra.gis.commons.StringUtils;
import org.apache.commons.lang.ArrayUtils;

import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.UUID;

/**
 * @author Zaur Muhametgaleev
 * @version 9/16/11 5:28 PM
 * @see GeoBase
 * @since Maxoptra (GeoBase)
 */
@Local(GeoBase.class)
@Stateless(name = GeoBase.JNDI_NAME)
public class GeoBaseBean implements GeoBase {
    // todo Z: брать эти настройки из конфигурации
    private final static String USER = "anonymous";
    private final static String PASSWORD = USER;
    private final static long EXPIRITY = 633968640000000000L;

    // todo Z: вынести в конфигурацию
    private final static String PATH = "http://%s/GeoStream/auth.aspx";

    private final int[] rearrangeIndexes = {3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15};

    private final Logger logger = new Logger(getClass());
    private final String ERR_MESSAGE = "GeoBase login token is not found. Check whether GeoBase server is alive.";

    @Override
    public String getAuthToken(@Context HttpServletRequest request, String geobaseIp) throws GeoException {
        String path = getGeoBaseAuthPath(geobaseIp);

        //use "0.0.0.0/0" to allow any ip address
        String urlStr = String.format(
                "%s?m=GetLoginToken&username=%s&ipAddress=0.0.0.0&mask=0&expiry=%s", path, USER, EXPIRITY);

        logger.info("Login token URL: " + urlStr);

        String loginToken = getURLData(urlStr);
        logger.info("Received Login token: " + loginToken);
        if (StringUtils.isEmpty(loginToken)) {
            throw new GeoException(ERR_MESSAGE);
        }

        String[] loginTokenSplit = loginToken.split(",");
        String guid = loginTokenSplit[0];
        String loginId = loginTokenSplit[1];
        logger.info("GUID: " + guid);

        byte[] rearrangedArray = getRearrangedArray(guid);

        String md5Hash = getMD5Hash(rearrangedArray);
        logger.info("MD5 hash: " + md5Hash);

        String newGUID = md5HashToGUID(md5Hash);
        logger.info("New GUID: " + newGUID);

        String authTokenURL = String.format("%s?m=GetAuthToken&logintok=%s&id=%s", path, newGUID, loginId);
        String authToken = getURLData(authTokenURL);
        logger.info("Auth Token: " + authToken);

        return authToken;
    }

    private String getGeoBaseAuthPath(String geobaseIp) {
        if (StringUtils.isEmpty(geobaseIp)) {
            throw new IllegalStateException();
        }

        return String.format(PATH, geobaseIp);
    }

    private byte[] getRearrangedArray(String guid) {
        if (guid == null || guid.length() != 36) {
            throw new IllegalArgumentException("GUID length must be equal to 36");
        }

        UUID uuid = UUID.fromString(guid);
        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
        bb.putLong(uuid.getMostSignificantBits());
        bb.putLong(uuid.getLeastSignificantBits());
        byte[] array = bb.array();
        return rearrangeArray(array);
    }

    private String[] toGUIDArray(String withoutHyphens) {
        String[] tmpArray = new String[withoutHyphens.length() / 2];

        int index = 0;
        for (int i = 0; i < withoutHyphens.length(); i += 2) {
            tmpArray[index++] = withoutHyphens.substring(i, i + 2);
        }

        // rearrange array
        return rearrangeStringArray(tmpArray);
    }

    private byte[] rearrangeArray(byte[] tmpArray) {
        int index;
        byte[] result = new byte[tmpArray.length];
        index = 0;
        for (int i : rearrangeIndexes) {
            result[index++] = tmpArray[i];
        }
        return result;
    }

    private String[] rearrangeStringArray(String... tmpArray) {
        int index;
        String[] result = new String[tmpArray.length];
        index = 0;
        for (int i : rearrangeIndexes) {
            result[index++] = tmpArray[i];
        }
        return result;
    }

    private String getMD5Hash(byte[] guidBytes) {
        // concatenate user, password and GUID
        byte[] userBytes = USER.getBytes();
        byte[] passwordBytes = PASSWORD.getBytes();
        ArrayList<Byte> concatenation = new ArrayList<Byte>();
        addBytes(concatenation, userBytes);
        addBytes(concatenation, passwordBytes);
        addBytes(concatenation, guidBytes);

        // convert list to byte[] array
        byte[] bytes = ArrayUtils.toPrimitive(concatenation.toArray(new Byte[concatenation.size()]));

        String md5Hash = null;

        // generate md5 hash
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            byte[] digest = md.digest();

            StringBuilder sb = new StringBuilder(32);
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            md5Hash = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return md5Hash;
    }

    private String md5HashToGUID(String md5Hash) {
        String[] chars = toGUIDArray(md5Hash);

        // add hyphens
        // result example: "b1cd379f-5371-4f1e-aa08-7919d3df5db1";
        String delimiter = "-";
        return chars[0] + chars[1] + chars[2] + chars[3] +
                delimiter +
                chars[4] + chars[5] +
                delimiter +
                chars[6] + chars[7] +
                delimiter +
                chars[8] + chars[9] +
                delimiter +
                chars[10] + chars[11] + chars[12] + chars[13] + chars[14] + chars[15];

    }

    private void addBytes(ArrayList<Byte> concatenation, byte[] array) {
        for (byte b : array) {
            concatenation.add(b);
        }
    }

    private String getURLData(String urlStr) throws GeoException{
        try {
            URL url = new URL(urlStr);
            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));

            StringBuilder result = new StringBuilder();
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                result.append(inputLine);
            }
            in.close();
            return result.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new GeoException(ERR_MESSAGE);
        }
    }

    public static void main(String[] args) {
        GeoBaseBean bean = new GeoBaseBean();
        try {
            bean.getAuthToken(null, "10.11.13.31");
        } catch (GeoException e) {
            e.printStackTrace();
        }
    }
}
